﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Nature
{    
    public abstract class IdealGasMixtureTimeController : IIdealGasMixtureTimeController
    {
        #region Abstract Methods

        protected abstract double Advance();

        protected abstract void OnStepComplete();

        protected abstract void Reset();

        protected abstract IdealGasMixtureTimeController Clone(); 

        #endregion

        #region Private Fields
        private IdealGasMixture m_mixtureSavePoint;
        #endregion

        protected IdealGasMixtureTimeController()
        {
            NextTimeIncrement = double.MaxValue;
        }

        #region Properties

        protected ReadOnlyIdealGasMixture MixtureSavePoint
        {
            get { return new ReadOnlyIdealGasMixture(m_mixtureSavePoint); }
        }

        protected IdealGasMixture Mixture
        {
            get;
            private set;
        }

        protected double SpeciesAbsDX { get; private set; }

        protected double MixtureAbsDT { get; private set; }

        protected double MixtureRelDT { get; private set; }

        public int Nsp { get; private set; }

        public double ProcessTime { get; private set; }

        public double LastTimeIncrement { get; private set; }

        protected double NextTimeIncrement { get; private set; }

        public TimeSpan TimeSpan { get { return TimeSpan.FromSeconds(ProcessTime); } }

        public TimeSpan LastTimeIncrementTimeSpan { get { return TimeSpan.FromSeconds(LastTimeIncrement); } }
 
        #endregion      

        

        protected virtual void Commit()
        {
            MixtureAbsDT = Math.Abs(Mixture.Temperature - m_mixtureSavePoint.Temperature);
            MixtureRelDT = MixtureAbsDT / Mixture.Temperature;
            SpeciesAbsDX = 0d;
            var x0 = m_mixtureSavePoint.SpeciesMoleFractions;
            var x1 = Mixture.SpeciesMoleFractions;
            for (int isp = 0; isp < Nsp; ++isp)
            {
                double dx = Math.Abs(x0[isp] - x1[isp]);
                SpeciesAbsDX = Math.Max(SpeciesAbsDX, dx);
            }
            m_mixtureSavePoint.UpdateTPY(
                Mixture.Temperature,
                Mixture.Pressure,
                Mixture.SpeciesMassFractions);
        }

        protected virtual void Rollback()
        {
            Mixture.UpdateTPY(
                m_mixtureSavePoint.Temperature,
                m_mixtureSavePoint.Pressure,
                m_mixtureSavePoint.SpeciesMassFractions);
        }        

        protected void ProposeNextTimeIncrement(double dt)
        {
            dt = Math.Max(1.0e-16, dt);
            NextTimeIncrement = Math.Min(dt, NextTimeIncrement);
        }

        protected void Reset(IdealGasMixture mixture)
        {
            ProcessTime = LastTimeIncrement = 0d;
            this.Mixture = mixture;
            this.Nsp = mixture.Nsp;
            NextTimeIncrement = double.MaxValue;
            if (m_mixtureSavePoint == null || m_mixtureSavePoint.Model != mixture.Model)
            {
                m_mixtureSavePoint = mixture.Clone();
                Reset();
            }
            else
            {
                m_mixtureSavePoint.UpdateTPY(
                    mixture.Temperature,
                    mixture.Pressure,
                    mixture.SpeciesMoleFractions);
            }
        }

        private double MoveNext()
        {
            LastTimeIncrement = this.Advance();
            ProcessTime += LastTimeIncrement;
            NextTimeIncrement = double.MaxValue;
            OnStepComplete();
            return LastTimeIncrement;
        }

        #region IIdealGasMixtureTimeController Members

        double IIdealGasMixtureTimeController.MoveNext()
        {
            return this.MoveNext();
        }

        void IIdealGasMixtureTimeController.Reset(IdealGasMixture mixture)
        {
            this.Reset(mixture);
        }

        #endregion

        #region ICloneable Members

        object ICloneable.Clone()
        {
            return this.Clone();
        }

        #endregion

        #region Static Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="array"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        protected static double[] Realloc(double[] array, int length)
        {
            return (array ?? (array = new double[length])).Length < length
                    ? new double[length]
                    : array;
        } 
        #endregion
    }

    /// <summary>
    /// 
    /// </summary>
    public interface IIdealGasMixtureTimeController : ICloneable
    {
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        double MoveNext();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="mixture"></param>
        void Reset(IdealGasMixture mixture);
    }

    /// <summary>
    /// 
    /// </summary>
    public struct IdealGasMixtureTimeAdapter
    {
        private IIdealGasMixtureTimeController m_controller;

        public IdealGasMixtureTimeAdapter(IIdealGasMixtureTimeController controller)
            : this()
        {
            m_controller = controller;
        }

        public void Reset(IdealGasMixture mixture)
        {
            if (mixture == null) { throw new ArgumentNullException("mixture"); }
            m_controller.Reset(mixture);
            Time = LastTimeIncrement = 0d;
        }

        public bool MoveNext()
        {
            if ((LastTimeIncrement = m_controller.MoveNext()) > 0d)
            {
                Time += LastTimeIncrement;
                return true;
            }
            return false;
        }

        public double Time { get; private set; }

        public double LastTimeIncrement { get; private set; }

        public TimeSpan TimeSpan { get { return TimeSpan.FromSeconds(Time); } }

        public TimeSpan LastTimeIncrementTimeSpan { get { return TimeSpan.FromSeconds(LastTimeIncrement); } }

    }    
    
    public static partial class Extensions
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="adapter"></param>
        /// <param name="timeIncrement"></param>
        /// <returns></returns>
        public static bool MoveNext(this IIdealGasMixtureTimeController adapter, out double timeIncrement)
        {
            return (timeIncrement = adapter.MoveNext()) > 0d;
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="updater"></param>
        /// <param name="mixture"></param>
        /// <param name="timeStop"></param>
        /// <returns></returns>
        public static IEnumerable<IdealGasMixtureTimePair> AsEnumerable(this IIdealGasMixtureTimeController updater, IdealGasMixture mixture, double timeStop = double.MaxValue)
        {
            return new TimeIdealGasMixtureCollection(updater, mixture);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public struct IdealGasMixtureTimePair
    {
        public double Time { get; private set; }

        public double LastTimeIncrement { get; private set; }

        public TimeSpan TimeSpan { get { return TimeSpan.FromSeconds(Time); } }

        public TimeSpan LastTimeIncrementTimeSpan { get { return TimeSpan.FromSeconds(LastTimeIncrement); } }

        public IdealGasMixtureTimePair(double time, double lastTimeIncrement, ReadOnlyIdealGasMixture mixture)
            : this()
        {
            this.Time = time;
            this.Mixture = mixture;
        }

        public IdealGasMixtureTimePair(double time, double lastTimeIncrement, IdealGasMixture mixture)
            : this(time, lastTimeIncrement, new ReadOnlyIdealGasMixture(mixture.Clone()))
        {
            
        }

        public ReadOnlyIdealGasMixture Mixture { get; private set; }
    }


    public struct IdealGasMixtureTimePairDiff
    {
        public IdealGasMixtureTimePairDiff(IdealGasMixtureTimePair pair1, IdealGasMixtureTimePair pair2) : this()
        {
            this.Pair1 = pair1;
            this.Pair2 = pair2;
        }

        public double TimeAbsDiff { get { return Math.Abs(Pair1.Time - Pair2.Time);} }

        public double TemperatureAbsDiff { get { return Math.Abs(Pair1.Mixture.Temperature - Pair2.Mixture.Temperature); } }

        public double TemperatureRelDiff { get { return 2.0 * TemperatureAbsDiff / (Pair1.Mixture.Temperature + Pair2.Mixture.Temperature); } }

        public double PressureAbsDiff { get { return Math.Abs(Pair1.Mixture.Pressure - Pair2.Mixture.Pressure); } }

        public double PressureRelDiff { get { return 2.0 * PressureAbsDiff / (Pair1.Mixture.Pressure + Pair2.Mixture.Pressure); } }


        public IdealGasMixtureTimePair Pair1 { get; private set; }

        public IdealGasMixtureTimePair Pair2 { get; private set; }
    }

    public interface IIdealGasMixtureTimePairObserverSwitch
    {
        bool IsValuable(IdealGasMixtureTimePair pair);
    }

    public abstract class IdealGasMixtureTimePairObserverSwitch : IIdealGasMixtureTimePairObserverSwitch
    {
        private IdealGasMixtureTimePair? m_prev;
        private IdealGasMixture m_mixture;

        protected abstract bool IsValuable(IdealGasMixtureTimePair prev, IdealGasMixtureTimePair curr);

        #region IIdealGasMixtureTimePairObserverSwitch Members

        public bool IsValuable(IdealGasMixtureTimePair pair)
        {
            if (m_prev.HasValue && m_prev.Value.Mixture.Model == pair.Mixture.Model) 
            {
                if (m_mixture == null || m_mixture.Model != pair.Mixture.Model)
                {
                    m_mixture = pair.Mixture.Model.CreateIdealGasMixture();
                    return true;
                }
                m_mixture.UpdateTPX(pair.Mixture.Temperature, pair.Mixture.Pressure, pair.Mixture.SpeciesMoleFractions);
                return IsValuable(m_prev.Value, new IdealGasMixtureTimePair(pair.Time, pair.LastTimeIncrement, m_mixture));
            }
            else 
            { 
                m_prev = pair;
                return true;
            }
        }

        #endregion
        
        public static IdealGasMixtureTimePairObserverSwitch Create(Func<IdealGasMixtureTimePairDiff, bool> isValuable)
        {
            return new IdealGasMixtureTimePairDiffObserverRelaySwitch(isValuable);
        }
    }

    class IdealGasMixtureTimePairDiffObserverRelaySwitch : IdealGasMixtureTimePairObserverSwitch
    {
        private readonly Func<IdealGasMixtureTimePairDiff, bool> _isValuable;

        public IdealGasMixtureTimePairDiffObserverRelaySwitch(Func<IdealGasMixtureTimePairDiff, bool> isValuable)
        {
            this._isValuable = isValuable;
        }

        protected override bool IsValuable(IdealGasMixtureTimePair prev, IdealGasMixtureTimePair curr)
        {
            return _isValuable(new IdealGasMixtureTimePairDiff(prev, curr));
        }
    }

    public class IdealGasMixtureTimePairRelayObserver : IObserver<IdealGasMixtureTimePair>
    {
        private readonly Action OnCompleted;
        private readonly Action<Exception> OnError;
        private readonly Action<IdealGasMixtureTimePair> OnNext;

        public IdealGasMixtureTimePairRelayObserver(Action<IdealGasMixtureTimePair> onNext, Action onCompleted = null, Action<Exception> onError = null)
        {
            this.OnNext = onNext;
            this.OnCompleted = onCompleted;
            this.OnError = onError;
        }

        #region IObserver<IdealGasMixtureTimePair> Members

        void IObserver<IdealGasMixtureTimePair>.OnCompleted()
        {
            if (this.OnCompleted != null) { this.OnCompleted(); }
        }

        void IObserver<IdealGasMixtureTimePair>.OnError(Exception error)
        {
            if (this.OnError != null) { this.OnError(error); }
        }

        void IObserver<IdealGasMixtureTimePair>.OnNext(IdealGasMixtureTimePair value)
        {
            this.OnNext(value);
        }

        #endregion
    }

    public class IdealGasMixtureTimePairAggregateObserver : IObserver<IdealGasMixtureTimePair>
    {
        private readonly List<IObserver<IdealGasMixtureTimePair>> Observers = new List<IObserver<IdealGasMixtureTimePair>>();
        private Task m_task;

        public IdealGasMixtureTimePairAggregateObserver() { }

        public IdealGasMixtureTimePairAggregateObserver(params IObserver<IdealGasMixtureTimePair>[] observers) 
        {
            Observers.AddRange(observers.Where(i=> i!=null).Distinct());
        }

        public IdealGasMixtureTimePairAggregateObserver(IEnumerable<IObserver<IdealGasMixtureTimePair>> observers)
        {
            Observers.AddRange(observers.Where(i => i != null).Distinct());
        }

        public void Add(Action<IdealGasMixtureTimePair> onNext, Action onCompleted = null, Action<Exception> onError = null)
        {
            Add( new IdealGasMixtureTimePairRelayObserver(onNext, onCompleted, onError));
        }

        public void Add(IObserver<IdealGasMixtureTimePair> observer)
        {
            if( observer == null || Observers.Contains(observer))
            {
                return;
            }
            else
            {
                Observers.Add(observer);
            }
        }


        public void AddRange(IEnumerable<IObserver<IdealGasMixtureTimePair>> observers)
        {
            foreach (var observer in observers)
            {
                this.Add(observer);
            }
        }


        public void OnCompleted()
        {
            if (m_task != null)
            {
                m_task.GetAwaiter().GetResult();
            }
            Observers.ForEach(i => i.OnCompleted());            
        }

        public void OnError(Exception error)
        {
            Observers.ForEach(i => i.OnError(error));
        }

        public void OnNext(IdealGasMixtureTimePair value)
        {
            Observers.ForEach(i=> i.OnNext(value));
        }

        public Task OnNextAsync(IdealGasMixtureTimePair value)
        {
            if (m_task != null)
            {
                return (m_task = m_task.ContinueWith(t => this.OnNext(value)));                
            }
            else 
            {
                return (m_task = Task.Run(() => this.OnNext(value)));
            }
        }

    }

    /// <summary>
    /// 
    /// </summary>
    public class TimeIdealGasMixtureCollection : IEnumerable<IdealGasMixtureTimePair>
    {
        private readonly IIdealGasMixtureTimeController m_controller;
        private readonly IdealGasMixture m_mixture;

        internal TimeIdealGasMixtureCollection(IIdealGasMixtureTimeController controller, IdealGasMixture mixture)
        {
            m_controller = controller;
            m_mixture = mixture.Clone();
        }


        public IEnumerator<IdealGasMixtureTimePair> GetEnumerator()
        {
            var controller = (IIdealGasMixtureTimeController)m_controller.Clone();
            var mixture = m_mixture.Clone();
            var adapter = new IdealGasMixtureTimeAdapter(controller);
            while (adapter.MoveNext())
            {
                yield return new IdealGasMixtureTimePair(adapter.Time, adapter.LastTimeIncrement, mixture);
            }

        }

        #region IEnumerable<TimeIdealGasMixturePair> Members

        IEnumerator<IdealGasMixtureTimePair> IEnumerable<IdealGasMixtureTimePair>.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
}

